package classfile

import (
	"gitee.com/microwww/jvm-go/jvm/tool"
	"encoding/binary"
	"fmt"
	"log"
)

type CommonAttr struct {
	cp                   ConstantPool
	attribute_name_index uint16
	attribute_length     uint32
}

func (c *CommonAttr) Name() string {
	return c.cp[c.attribute_name_index].(*CONSTANT_Utf8_info).Utf8m()
}

func (c *CommonAttr) Read(bf *tool.Buffer) {
	c.attribute_name_index = binary.BigEndian.Uint16(bf.Next(2))
	c.attribute_length = binary.BigEndian.Uint32(bf.Next(4))
}

type ConstantValueAttr struct {
	CommonAttr
	constantvalue_index uint16
}

func (c *ConstantValueAttr) ReadAttribute(bf *tool.Buffer) {
	if c.attribute_length != 2 {
		panic(fmt.Sprintf("ConstantValueAttr error attribute_length != 2"))
	}
	c.constantvalue_index = binary.BigEndian.Uint16(bf.Next(2))
}

type DeprecatedAttr struct {
	CommonAttr
}

func (c *DeprecatedAttr) ReadAttribute(*tool.Buffer) {
	if c.attribute_length != 0 {
		panic(fmt.Sprintf("DeprecatedAttr error attribute_length != 0"))
	}
}

type SyntheticAttr struct {
	CommonAttr
}

func (c *SyntheticAttr) ReadAttribute(*tool.Buffer) {
	if c.attribute_length != 0 {
		panic(fmt.Sprintf("SyntheticAttr error attribute_length != 0"))
	}
}

type InnerClassesAttr struct {
	CommonAttr
	number_of_classes uint16
	classes           []InnerClass
}

type InnerClass struct {
	inner_class_info_index   uint16
	outer_class_info_index   uint16
	inner_name_index         uint16
	inner_class_access_flags uint16
}

func (c *InnerClassesAttr) ReadAttribute(bf *tool.Buffer) {
	c.number_of_classes = binary.BigEndian.Uint16(bf.Next(2))
	cs := make([]InnerClass, c.number_of_classes)
	for i := range cs {
		ic := InnerClass{}
		ic.inner_class_info_index = binary.BigEndian.Uint16(bf.Next(2))
		ic.outer_class_info_index = binary.BigEndian.Uint16(bf.Next(2))
		ic.inner_name_index = binary.BigEndian.Uint16(bf.Next(2))
		ic.inner_class_access_flags = binary.BigEndian.Uint16(bf.Next(2))
		cs[i] = ic
	}
	c.classes = cs
}

type SignatureAttr struct {
	CommonAttr
	signature_index uint16
}

func (c *SignatureAttr) ReadAttribute(bf *tool.Buffer) {
	c.signature_index = binary.BigEndian.Uint16(bf.Next(2))
}

type SourceFileAttr struct {
	CommonAttr
	sourcefile_index uint16
}

func (c *SourceFileAttr) ReadAttribute(bf *tool.Buffer) {
	c.sourcefile_index = binary.BigEndian.Uint16(bf.Next(2))
}

type AttrBytes struct {
	CommonAttr
	info []byte
}

func (c *AttrBytes) ReadAttribute(bf *tool.Buffer) {
	c.info = bf.Next(int(c.attribute_length))
}

type ExceptionTable struct {
	start_pc   uint16
	end_pc     uint16
	handler_pc uint16
	catch_type uint16
}

type ExceptionsAttribute struct {
	CommonAttr
	number_of_exceptions uint16
	exceptions           []ExceptionIndexTable
}

func (c *ExceptionsAttribute) ReadAttribute(bf *tool.Buffer) {
	c.number_of_exceptions = binary.BigEndian.Uint16(bf.Next(2))
	ex := make([]ExceptionIndexTable, c.number_of_exceptions)
	for i := range ex {
		e := ExceptionIndexTable{}
		e.Read(bf)
		ex[i] = e
	}
}

type StackMapTableAttr struct {
	AttrBytes
	number_of_entries uint16
	entries           []StackMapFrame
}

type StackMapFrame struct {
}

func (c *StackMapTableAttr) ReadAttribute(bf *tool.Buffer) {
	c.AttrBytes.ReadAttribute(bf)
	// c.number_of_entries = binary.BigEndian.Uint16(c.info[:2])
	// c.entries = make([]StackMapFrame, c.number_of_entries)
}

type ExceptionIndexTable = StringInfo

type CodeAttribute struct {
	CommonAttr
	max_stack              uint16
	max_locals             uint16
	code_length            uint32
	code                   []byte
	exception_table_length uint16
	exception_tables       []ExceptionTable
	attributes_count       uint16
	attribute_info         []AttributeInfo
}

func (c *CodeAttribute) ReadAttribute(bf *tool.Buffer) {
	c.max_stack = binary.BigEndian.Uint16(bf.Next(2))
	c.max_locals = binary.BigEndian.Uint16(bf.Next(2))
	c.code_length = binary.BigEndian.Uint32(bf.Next(4))
	c.code = bf.Next(int(c.code_length))
	{
		c.exception_table_length = binary.BigEndian.Uint16(bf.Next(2))
		et := make([]ExceptionTable, c.exception_table_length)
		for i := uint16(0); i < c.exception_table_length; i++ {
			e := ExceptionTable{}
			e.start_pc = binary.BigEndian.Uint16(bf.Next(2))
			e.end_pc = binary.BigEndian.Uint16(bf.Next(2))
			e.handler_pc = binary.BigEndian.Uint16(bf.Next(2))
			e.catch_type = binary.BigEndian.Uint16(bf.Next(2))
			et[i] = e
		}
		c.exception_tables = et
	}
	{
		c.attributes_count = binary.BigEndian.Uint16(bf.Next(2))
		et := make([]AttributeInfo, c.attributes_count)
		for i := uint16(0); i < c.attributes_count; i++ {
			et[i] = ReadAttribute(bf, c.cp)
		}
		c.attribute_info = et
	}

}

func (c *CodeAttribute) Code() []byte {
	return c.code
}

func (c *CodeAttribute) MaxLocals() uint16 {
	return c.max_locals
}

func (c *CodeAttribute) MaxStack() uint16 {
	return c.max_stack
}

type StackMapTableAttribute struct {
	CommonAttr
	number_of_entries uint16
	entries           []byte
}

func (c *StackMapTableAttribute) ReadAttribute(bf *tool.Buffer) {
	c.number_of_entries = binary.BigEndian.Uint16(bf.Next(2))
	c.entries = bf.Next(int(c.number_of_entries))
	panic("Not find where to read StackMapTableAttribute.entries")
}

type LineNumberTableAttr struct {
	CommonAttr
	line_number_table_length uint16
	line_number_table        []LineNumber
}

type LineNumber struct {
	start_pc    uint16
	line_number uint16
}

func (c *LineNumberTableAttr) ReadAttribute(bf *tool.Buffer) {
	c.line_number_table_length = binary.BigEndian.Uint16(bf.Next(2))
	lnt := make([]LineNumber, c.line_number_table_length)
	for i := uint16(0); i < c.line_number_table_length; i++ {
		v := LineNumber{}
		v.start_pc = binary.BigEndian.Uint16(bf.Next(2))
		v.line_number = binary.BigEndian.Uint16(bf.Next(2))
		lnt[i] = v
	}
	c.line_number_table = lnt
}

type LocalVariableTableAttr struct {
	CommonAttr
	local_variable_table_length uint16
	local_variable_table        []LocalVariableAttr
}

type LocalVariableAttr struct {
	start_pc         uint16
	length           uint16
	name_index       uint16
	descriptor_index uint16
	index            uint16
}

func (c *LocalVariableTableAttr) ReadAttribute(bf *tool.Buffer) {
	c.local_variable_table_length = binary.BigEndian.Uint16(bf.Next(2))
	lnt := make([]LocalVariableAttr, c.local_variable_table_length)
	for i := uint16(0); i < c.local_variable_table_length; i++ {
		v := LocalVariableAttr{}
		v.start_pc = binary.BigEndian.Uint16(bf.Next(2))
		v.length = binary.BigEndian.Uint16(bf.Next(2))
		v.name_index = binary.BigEndian.Uint16(bf.Next(2))
		v.descriptor_index = binary.BigEndian.Uint16(bf.Next(2))
		v.index = binary.BigEndian.Uint16(bf.Next(2))
		lnt[i] = v
	}
	c.local_variable_table = lnt
}

func ReadAttribute(bf *tool.Buffer, cp ConstantPool) AttributeInfo {
	ca := CommonAttr{cp: cp}
	ca.Read(bf)

	v := cp[ca.attribute_name_index]
	if v == nil {
		return nil
	}
	name := v.(*CONSTANT_Utf8_info).Utf8m()
	// log.Default().Printf("Parse attr type `%v`", name)

	// 有 23 中属性类型
	switch name {
	case "ConstantValue":
		attr := ConstantValueAttr{CommonAttr: ca}
		attr.ReadAttribute(bf)
		return &attr
	case "Code":
		attr := CodeAttribute{CommonAttr: ca}
		attr.ReadAttribute(bf)
		return &attr
	case "StackMapTable":
		attr := StackMapTableAttr{AttrBytes: AttrBytes{CommonAttr: ca}}
		attr.ReadAttribute(bf)
		return &attr
	case "Exceptions":
		attr := ExceptionsAttribute{CommonAttr: ca}
		attr.ReadAttribute(bf)
		return &attr
	case "BootstrapMethods":
	case "InnerClasses":
		attr := InnerClassesAttr{CommonAttr: ca}
		attr.ReadAttribute(bf)
		return &attr
	case "EnclosingMethod":
	case "Synthetic":
		attr := SyntheticAttr{CommonAttr: ca}
		attr.ReadAttribute(bf)
		return &attr
	case "Signature":
		attr := SignatureAttr{CommonAttr: ca}
		attr.ReadAttribute(bf)
		return &attr
	case "RuntimeVisibleAnnotations":
	case "RuntimeInvisibleAnnotations":
	case "RuntimeVisibleParameterAnnotations":
	case "RuntimeInvisibleParameterAnnotations":
	case "RuntimeVisibleTypeAnnotations":
	case "RuntimeInvisibleTypeAnnotations":
	case "AnnotationDefault":
	case "MethodParameters":
	case "SourceFile":
		attr := SourceFileAttr{CommonAttr: ca}
		attr.ReadAttribute(bf)
		return &attr
	case "SourceDebugExtension":
	case "LineNumberTable":
		attr := LineNumberTableAttr{CommonAttr: ca}
		attr.ReadAttribute(bf)
		return &attr
	case "LocalVariableTable":
		attr := LocalVariableTableAttr{CommonAttr: ca}
		attr.ReadAttribute(bf)
		return &attr
	case "LocalVariableTypeTable":
	case "Deprecated":
		attr := DeprecatedAttr{CommonAttr: ca}
		attr.ReadAttribute(bf)
		return &attr
	}
	attr := AttrBytes{CommonAttr: ca}
	attr.ReadAttribute(bf)
	log.Default().Printf("Unsupported type `%v`, Using default read tool: %v", name, attr.attribute_length)
	return &attr
	// panic(fmt.Sprintf("Attribute `%v` is unsupported !", name))
}
